package cn.juque.luceneplus.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.juque.common.exception.AppException;
import cn.juque.luceneplus.constants.BasicIndexFieldEnum;
import cn.juque.luceneplus.constants.IndexEnum;
import cn.juque.luceneplus.constants.LuceneConfig;
import cn.juque.luceneplus.constants.LuceneMsgEnum;
import cn.juque.luceneplus.dto.FieldTemplateDTO;
import cn.juque.luceneplus.dto.IndexTemplateDTO;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.StringField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

/**
 * @author nuoka
 * @version 1.0.0
 * <li>IntelliJ IDEA</li>
 * <li></li>
 * @date 2021/10/4 0:59
 **/
@Slf4j
@Service("indexPlusService")
public class IndexPlusService {

    /**
     * 避免频繁创建
     */
    private Map<String, Directory> directoryMap;

    @Value("${lucene.index.directory}")
    private String indexDirectory;

    /**
     * 删除索引
     * @param indexName 索引名称
     * @throws IOException IOException
     */
    public void deleteIndex(String indexName) throws IOException {
        if(Boolean.TRUE.equals(this.existIndexInfo(indexName))) {
            Directory directory = this.getDirectory(indexName);
            try(IndexWriter indexWriter = new IndexWriter(directory, new IndexWriterConfig(new StandardAnalyzer()))) {
                indexWriter.deleteAll();
            }
            directory = this.getDirectory(IndexEnum.BASIC_INDEX.getName());
            try(IndexWriter indexWriter = new IndexWriter(directory, new IndexWriterConfig(new StandardAnalyzer()))) {
                indexWriter.deleteDocuments(new Term(BasicIndexFieldEnum.INDEX_NAME.getValue(), indexName));
            }
        }
    }

    /**
     * 判断索引是否已经存在
     * @param indexName 索引名称
     * @return true：存在
     */
    public Boolean existIndexInfo(String indexName) {
        if(StrUtil.isEmpty(indexName)) {
            throw new AppException(LuceneMsgEnum.INDEX_NAME_MUST_NOT_NULL);
        }
        try {
            return DirectoryReader.indexExists(this.getDirectory(indexName));
        } catch (IOException e) {
            // 索引目录不存在，则会抛出 IOException
            return false;
        }
    }

    /**
     * 获取索引信息
     * @param indexName 索引名称
     * @return List
     * @throws IOException IOException
     */
    public List<Document> getIndexInfo(String indexName) throws IOException {
        if(StrUtil.isEmpty(indexName)) {
            throw new AppException(LuceneMsgEnum.INDEX_NAME_MUST_NOT_NULL);
        }
        if(!Boolean.TRUE.equals(this.existIndexInfo(IndexEnum.BASIC_INDEX.getName()))) {
            return CollUtil.newArrayList();
        }
        Directory directory = this.getDirectory(IndexEnum.BASIC_INDEX.getName());
        try(IndexReader indexReader = DirectoryReader.open(directory)) {
            Term term = new Term(BasicIndexFieldEnum.INDEX_NAME.getValue(), indexName);
            TermQuery termQuery = new TermQuery(term);
            IndexSearcher indexSearcher = new IndexSearcher(indexReader);
            TopDocs topDocs = indexSearcher.search(termQuery, LuceneConfig.FIELD_MAX_COUNT);
            ScoreDoc[] scoreDocs = topDocs.scoreDocs;
            List<Document> documentList = CollUtil.newArrayList();
            for (ScoreDoc scoreDoc : scoreDocs) {
                documentList.add(indexSearcher.doc(scoreDoc.doc));
            }
            return documentList;
        }
    }

    /**
     * 获取索引所在的目录
     * @param name 索引名称
     * @return Directory
     */
    public Directory getDirectory(String name) {
        if(StrUtil.isEmpty(name)) {
            throw new AppException(LuceneMsgEnum.INDEX_NAME_MUST_NOT_NULL);
        }
        if(null == this.directoryMap) {
            directoryMap = MapUtil.newHashMap(16);
        }
        if(this.directoryMap.containsKey(name)) {
            return this.directoryMap.get(name);
        }
        try {
            Directory directory = FSDirectory.open(Paths.get(this.indexDirectory, name));
            this.directoryMap.put(name, directory);
            return directory;
        } catch (Exception e) {
            log.error("open directory error, index name: {}", name, e);
            throw new AppException(LuceneMsgEnum.OPEN_INDEX_ERROR);
        }
    }

    /**
     * 保存索引信息
     * @param indexTemplateDTO 索引信息
     * @throws IOException IOException
     */
    public void initBasicIndex(IndexTemplateDTO indexTemplateDTO) throws IOException {
        // 创建分词器
        StandardAnalyzer standardAnalyzer = new StandardAnalyzer();
        // 创建writer
        IndexWriterConfig indexWriterConfig = new IndexWriterConfig(standardAnalyzer);
        try(IndexWriter indexWriter = new IndexWriter(this.getDirectory(IndexEnum.BASIC_INDEX.getName()), indexWriterConfig)) {
            for (FieldTemplateDTO t : indexTemplateDTO.getFieldDTOList()) {
                Field indexNameField = new StringField(BasicIndexFieldEnum.INDEX_NAME.getValue(), indexTemplateDTO.getIndexName(), Store.YES);
                Field fieldNameField = new StringField(BasicIndexFieldEnum.FIELD_NAME.getValue(), t.getFieldName(), Store.YES);
                Field fieldTypeField = new StringField(BasicIndexFieldEnum.FIELD_TYPE.getValue(), t.getFieldType().getType(), Store.YES);
                Document doc = new Document();
                doc.add(indexNameField);
                doc.add(fieldNameField);
                doc.add(fieldTypeField);
                Store store = null == t.getStore() ? Store.NO : t.getStore();
                Field fieldStoreField = new StringField(BasicIndexFieldEnum.FIELD_STORE.getValue(), store.name(), Store.YES);
                doc.add(fieldStoreField);
                Field fieldPointField = new StringField(BasicIndexFieldEnum.FIELD_POINT.getValue(), null == t.getPoint() ? "false" : t.getPoint().toString(), Store.YES);
                doc.add(fieldPointField);
                Field fieldSortField = new StringField(BasicIndexFieldEnum.FIELD_SORT.getValue(), null == t.getIsSort() ? "false" : t.getIsSort().toString(), Store.YES);
                doc.add(fieldSortField);
                if(null !=t.getAnalyzer()) {
                    Field analyzerField = new StringField(BasicIndexFieldEnum.FIELD_ANALYZER.getValue(), t.getAnalyzer().name(), Store.YES);
                    doc.add(analyzerField);
                }
                indexWriter.addDocument(doc);
                indexWriter.commit();
            }
        }
    }
}
